package cn.pku.ss.sirx.sna.ib;

import java.util.Collection;
import java.util.Date;

import cn.pku.ss.sirx.sna.ib.bean.EnqueueEvent;
import cn.pku.ss.sirx.sna.ib.bean.Influence;
import cn.pku.ss.sirx.sna.ib.bean.Message;
import cn.pku.ss.sirx.sna.ib.bean.MessageBoard;
import cn.pku.ss.sirx.sna.ib.bean.ReplyTree;
import cn.pku.ss.sirx.sna.ib.bean.ReplyTreeNode;
import cn.pku.ss.sirx.sna.ib.db.InfluenceDao;

/**
 * Calculate influence when an enqueue event occurs.
 * 
 * @author WangXiao
 * @version 1.0
 */
public class InfluenceCalculator implements IEnqueueListener {

	/**
	 * Constructor.
	 */
	public InfluenceCalculator(ReplyTreeQueue replyTreeQueue) {
		this.replyTreeQueue = replyTreeQueue;
		this.messageBoard = replyTreeQueue.getMessageBoard();
		this.calculator = new Calculator();
	}

	/**
	 * @see cn.pku.ss.sirx.sna.ib.IEnqueueListener#onEnqueue(cn.pku.ss.sirx.sna.ib.bean.EnqueueEvent)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public void onEnqueue(EnqueueEvent enqueueEvent) {
		// finish build work
		if (enqueueEvent == EnqueueEvent.EMPTY) {
			for (ReplyTree replyTree : this.replyTreeQueue) {
				this.calculator.calculate(replyTree);
			}
			this.replyTreeQueue.clear();
			// store calculate result
			InfluenceDao.getInstance().updateInfluence(
					(Collection<Influence>) this.calculator
							.getCalculateResult());
		}
		// enqueue a reply tree
		else if (enqueueEvent.isReplyTreeEnqueued()) {
			this.dequeueOverdues(enqueueEvent);
			if (!enqueueEvent.isEnqueuedSuccessfully()) {
				enqueueEvent.getReplyTreeQueue().enqueue(
						enqueueEvent.getEnqueuedReplyTree());
			}
		}
		// enqueue a reply tree node (lost node)
		else if (enqueueEvent.isReplyTreeNodeEnqueued()) {
			this.calculator.calculate(enqueueEvent.getEnqueuedReplyTreeNode());
		}
	}

	// ////////////////////////////////////////////////////////////////////////////

	private void dequeueOverdues(EnqueueEvent enqueueEvent) {
		ReplyTreeQueue replyTreeQueue = enqueueEvent.getReplyTreeQueue();
		ReplyTree replyTree = replyTreeQueue.peek();
		ReplyTree enqueuedReplyTree = replyTreeQueue.rear();
		while (replyTree != enqueuedReplyTree) {
			if (this
					.isOverdue(replyTree.getDate(), enqueuedReplyTree.getDate())) {
				replyTree = replyTree.getNext();
				this.calculator.calculate(replyTreeQueue.dequeue());
			} else {
				break;
			}
		}
	}

	private boolean isOverdue(Date previousDate, Date current) {
		return (current.getTime() - previousDate.getTime()) / 86400000 > this.OVERDUE_DAYS_COUNT;
	}

	// ////////////////////////////////////////////////////////////////////////////

	private class Calculator implements ICalculator {

		@Override
		public void calculate(Object object) {
			if (object instanceof ReplyTree) {
				this.calculate((ReplyTree) object);
			} else if (object instanceof ReplyTreeNode) {
				this.calculate((ReplyTreeNode) object);
			}
		}

		@Override
		public Object getCalculateResult() {
			return this.container.getCollection();
		}

		// ////////////////////////////////////////////////////////////////////////////

		private void calculate(ReplyTree replyTree) {
			this.calculateTree(replyTree.getRoot());
		}

		private double calculateTree(ReplyTreeNode root) {
			Message message = root.getMessage();
			double influenceValue = this.getMessageValue(message);
			for (ReplyTreeNode child : root.getChildren()) {
				influenceValue += 1.5 * calculateTree(child);
			}
			this.container.add(new Influence(message.getToId(), message
					.getFromId(), message.getTime(), influenceValue));
			return influenceValue;
		}

		private void calculate(ReplyTreeNode replyTreeNode) {
			Message message = replyTreeNode.getMessage();
			double influenceValue = this.getMessageValue(message);
			// A -> B
			if (message.getFromId().equals(this.ownerId)) {
				this.container.add(new Influence(this.ownerId, message
						.getToId(), message.getTime(), influenceValue));
			}
			// B -> C
			else {
				this.container.add(new Influence(this.ownerId, message
						.getFromId(), message.getTime(), influenceValue));
				this.container.add(new Influence(this.ownerId, message
						.getToId(), message.getTime(), influenceValue));
			}
		}

		private double getMessageValue(Message message) {
			double result = this.getMessageContentValue(message.getContent());
			if (message.isNewReply()) {
				result *= 1.5;
			}
			return result;
		}

		private double getMessageContentValue(String content) {
			if (content != null) {
				return (((double) content.length()) / EFFECT_MESSAGE_SEGMENT_SIZE);
			}
			return .0;
		}

		// ////////////////////////////////////////////////////////////////////////////

		private InfluenceContainer container = new InfluenceContainer();
		private String ownerId = messageBoard.getOwnerId();
		private final int EFFECT_MESSAGE_SEGMENT_SIZE = 40;
	}

	private ReplyTreeQueue replyTreeQueue;
	private MessageBoard messageBoard;
	private ICalculator calculator;
	private final int OVERDUE_DAYS_COUNT = 7; // a week

}
